
/* GCSx
** CONFIG.CPP
**
** Configuration loading and saving
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

Config* config = NULL;

Config::Config() : configS(), configN(), configK(), configFile() { start_func
    configLoaded = 0;
    needsSaving = 0;
}

Config::~Config() { start_func
}

void Config::loadConfig() { start_func
    if (!configLoaded) {
        FILE* file = NULL;
        char* value = NULL;

        // Config filename
        createFilename(mainDir->c_str(), FILENAME_CFG, configFile);

        initConfigDefaults(configN);
        defaultShortcuts();

        // Load from file
        file = fopen(configFile.c_str(), "rb");
        if (file) {
            while (!feof(file)) {
                // We manually break it into 8bits for cross-compatibility
                int setting = fgetc(file) | (fgetc(file) << 8);
                if (setting == CONFIG_NO_SETTING) break;

                if ((setting >= CONFIG_FIRST_STRING_SETTING) && 
                    (setting <= CONFIG_LAST_STRING_SETTING)) {
                    unsigned int len = fgetc(file);
                    value = new char[len];
                    if (fread(value, 1, len, file) == len) {
                        configS[setting] = value;
                    }
                    delete[] value;
                    value = NULL;
                }
                else if (setting <= CONFIG_MAX_SETTING) {
                    // We manually break it into 8bits for cross-compatibility
                    Sint32 result = fgetc(file) | (fgetc(file) << 8) | (fgetc(file) << 16) | (fgetc(file) << 24);

                    // This handles shortcut key reverses, etc.
                    write(setting, result);
                }
            }
            fclose(file);
        }
        
        configLoaded = 1;
        needsSaving = 0;
    }
}

// Just marks us as needing saving
void Config::saveConfig() { start_func
    needsSaving = 1;
}

void Config::performSave() { start_func
    if (needsSaving) {
        FILE* file = NULL;
        needsSaving = 0;

        // Save to file
        file = fopen(configFile.c_str(), "wb");
        if (file) {
            map<Uint16, string>::iterator endS = configS.end();
            for (map<Uint16, string>::iterator pos = configS.begin(); pos != endS; ++pos) {
                int code = (*pos).first;
                // We manually break it into 8bits for cross-compatibility
                fputc(code & 0xFF, file);
                fputc((code >> 8) & 0xFF, file);
                fputc((*pos).second.size(), file);
                fwrite((*pos).second.c_str(), 1, (*pos).second.size(), file);
            }
            map<Uint16, Sint32>::iterator endN = configN.end();
            for (map<Uint16, Sint32>::iterator pos = configN.begin(); pos != endN; ++pos) {
                int code = (*pos).first;
                if ((code < CONFIG_FIRST_VOLATILE_SETTING) || (code >= CONFIG_ADD_SHORTCUT)) {
                    Sint32 value = (*pos).second;
                    // We manually break it into 8bits for cross-compatibility
                    fputc(code & 0xFF, file);
                    fputc((code >> 8) & 0xFF, file);
                    fputc(value & 0xFF, file);
                    fputc((value >> 8) & 0xFF, file);
                    fputc((value >> 16) & 0xFF, file);
                    fputc((value >> 24) & 0xFF, file);
                }
            }
            fputc(CONFIG_NO_SETTING & 0xFF, file);
            fputc((CONFIG_NO_SETTING >> 8) & 0xFF, file);
            fclose(file);
        }
    }
}

Sint32 Config::readNum(Uint16 key) { start_func
    assert(key <= CONFIG_MAX_SETTING);
    assert(key > CONFIG_NO_SETTING);
    assert((key < CONFIG_FIRST_STRING_SETTING) || (key > CONFIG_LAST_STRING_SETTING));

    loadConfig();

    map<Uint16, Sint32>::iterator found = configN.find(key);
    if (found == configN.end()) return 0;
    return (*found).second;
}

const string& Config::readStr(Uint16 key) { start_func
    assert(key <= CONFIG_MAX_SETTING);
    assert(key > CONFIG_NO_SETTING);
    assert((key >= CONFIG_FIRST_STRING_SETTING) && (key <= CONFIG_LAST_STRING_SETTING));

    loadConfig();

    map<Uint16, string>::iterator found = configS.find(key);
    if (found == configS.end()) return blankString;
    return (*found).second;
}

void Config::addKey(Sint32 key, Uint16 cmd) { start_func
    configK[key].push_back(cmd);
}

void Config::delKey(Sint32 key, Uint16 cmd) { start_func
    SKeyMap::iterator pos = configK.find(key);
    if (pos != configK.end()) {
        vector<Uint16>::iterator end2 = (*pos).second.end();
        for (vector<Uint16>::iterator pos2 = (*pos).second.begin(); pos2 != end2; ++pos2) {
            if (*pos2 == cmd) {
                (*pos).second.erase(pos2);
                break;
            }
        }
        if ((*pos).second.empty()) {
            configK.erase(pos);
        }
    }
}

void Config::write(Uint16 key, Sint32 value) { start_func
    assert(key <= CONFIG_MAX_SETTING);
    assert(key > CONFIG_NO_SETTING);
    assert((key < CONFIG_FIRST_STRING_SETTING) || (key > CONFIG_LAST_STRING_SETTING));

    // If this is a shortcut key, update shortcut reverse lookup
    if (key >= CONFIG_ADD_SHORTCUT) {
        Uint16 shortkey = (key - CONFIG_ADD_SHORTCUT) % CONFIG_ADD_SHORTCUT_ALTERNATE;
        
        // Previous key? Erase
        map<Uint16, Sint32>::iterator found = configN.find(key);
        if (found != configN.end()) {
            delKey((*found).second, shortkey);
        }
        
        // Add new key
        addKey(value, shortkey);

        // If this was a "first" (non alternate) shortcut key, we need
        // to delete any alternates that may be there also!
        if (key < CONFIG_ADD_SHORTCUT + CONFIG_ADD_SHORTCUT_ALTERNATE) {
            int altKey = key + CONFIG_ADD_SHORTCUT_ALTERNATE;
            map<Uint16, Sint32>::iterator endN = configN.end();
        
            while ((found = configN.find(altKey)) != endN) {
                delKey((*found).second, shortkey);
                configN.erase(found);
                altKey += CONFIG_ADD_SHORTCUT_ALTERNATE;
            }
        }
    }
    // Skip saving if no change
    else if (configN[key] == value) return;

    // Store setting
    configN[key] = value;
    if ((key < CONFIG_FIRST_VOLATILE_SETTING) || (key >= CONFIG_ADD_SHORTCUT))
        saveConfig();
}

void Config::write(Uint16 key, const string& value) { start_func
    assert(key <= CONFIG_MAX_SETTING);
    assert(key > CONFIG_NO_SETTING);
    assert((key >= CONFIG_FIRST_STRING_SETTING) && (key <= CONFIG_LAST_STRING_SETTING));
    
    if (value.size() > 255) return;

    configS[key] = value;
    saveConfig();
}

void Config::clearShortcuts() { start_func
    map<Uint16, Sint32>::iterator end = configN.end();
    map<Uint16, Sint32>::iterator next;
    for (map<Uint16, Sint32>::iterator pos = configN.begin(); pos != end; pos = next) {
        // Get next element in case we delete
        next = pos;
        ++next;
        
        // Is this in the shortcuts range?
        if ((*pos).first >= CONFIG_ADD_SHORTCUT) {
            // Remove
            configN.erase(pos);
        }
    }
    
    configK.clear();
}

void Config::defaultShortcuts() { start_func
    clearShortcuts();

    map<Uint16, Sint32>::iterator end = configN.end();
    for (int pos = 0; shortcutDefault[pos].cmd; ++pos) {
        int addAlt = 0;
        while (configN.find(shortcutDefault[pos].cmd + CONFIG_ADD_SHORTCUT + addAlt) != end) {
            addAlt += CONFIG_ADD_SHORTCUT_ALTERNATE;
        }
        
        // Found an empty position; make sure we're within the max alternates
        if (addAlt / CONFIG_ADD_SHORTCUT_ALTERNATE <= CONFIG_MAX_SHORTCUT_ALTERNATE) {
            // Add to config, update reverse lookups
            write(shortcutDefault[pos].cmd + CONFIG_ADD_SHORTCUT + addAlt,
                  combineKey(shortcutDefault[pos].key, shortcutDefault[pos].mod));
        }
    }
}

int Config::readShortcut(Uint16 key, Uint16 mod, int pos) { start_func
    SKeyMap::iterator posK = configK.find(combineKey(key, mod));
    if (posK != configK.end()) {
        int size = (*posK).second.size();
        if (pos == 0) return size;
        if (pos <= size) return (*posK).second[pos - 1];
    }
    return 0;
}

Sint32 Config::readShortcut(Uint16 cmd) { start_func
    return readNum(cmd + CONFIG_ADD_SHORTCUT);
}

